using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Text.RegularExpressions;
using System.Web.Security;
using Common.Logging;
using WebMatrix.WebData;
using System.Linq;

namespace DbExtendedProvider
{
	public abstract class DbExtendedMembershipProvider<TDbCommander, TEncryptor> : ExtendedMembershipProvider
		where TDbCommander : class, IDbCommander, new()
		where TEncryptor : class, IEncryption, new()
	{

		#region properties and fields
		private const int TokenBitSize = 16;
		private static readonly ILog Logger = LogManager.GetCurrentClassLogger();
		// ReSharper disable InconsistentNaming
		protected string ProviderName = "PgExtendedMembershipProvider";
		protected bool enablePasswordReset;
		protected bool enablePasswordRetrieval;
		protected byte[] encryptionKey;
		protected int loginHistoryRetentionInDays;
		protected int loginHistoryRetentionInRecords;
		protected int maxInvalidPasswordAttempts;
		protected int maxSaltCharacters;
		protected int minRequiredNonAlphaNumericCharacters;
		protected int minRequiredPasswordLength;
		protected int minSaltCharacters;
		protected int passwordAttemptWindow;
		protected MembershipPasswordFormat passwordFormat;
		protected string passwordStrengthRegularExpression;
		protected bool requiresQuestionAndAnswer;
		protected bool requiresUniqueEmail;
		protected int sessionTime;
		protected int lockoutTime;
		// ReSharper restore InconsistentNaming

		/// <summary>
		///     The length of time to retain login records for a specific user.
		/// </summary>
		public int LoginHistoryRetentionInDays
		{
			get { return loginHistoryRetentionInDays; }
		}

		/// <summary>
		///     The number of login records to retain for a specific user.
		/// </summary>
		public int LoginHistoryRetentionInRecords
		{
			get { return loginHistoryRetentionInRecords; }
		}

		protected bool IsInitialized { get; set; }
		protected readonly TDbCommander Commander = new TDbCommander();
		protected int LockoutTime
		{
			get { return lockoutTime; }
			set { lockoutTime = value; }
		}

		protected readonly TEncryptor Encryptor = new TEncryptor();

		public override string Description
		{
			get { return string.Format("PgExtendedMembershipProvider v{0}", GetType().Assembly.GetName().Version); }
		}

		/// <summary>
		///     Indicates whether the membership provider is configured to allow users to retrieve their passwords.
		/// </summary>
		/// <returns>
		///     true if the membership provider is configured to support password retrieval; otherwise, false. The default is false.
		/// </returns>
		public override bool EnablePasswordRetrieval
		{
			get { return enablePasswordRetrieval; }
		}

		/// <summary>
		///     Indicates whether the membership provider is configured to allow users to reset their passwords.
		/// </summary>
		/// <returns>
		///     true if the membership provider supports password reset; otherwise, false. The default is true.
		/// </returns>
		public override bool EnablePasswordReset
		{
			get { return enablePasswordReset; }
		}

		/// <summary>
		///     Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval.
		/// </summary>
		/// <returns>
		///     true if a password answer is required for password reset and retrieval; otherwise, false. The default is true.
		/// </returns>
		public override bool RequiresQuestionAndAnswer
		{
			get { return requiresQuestionAndAnswer; }
		}

		/// <summary>
		///     The name of the application using the custom membership provider.
		/// </summary>
		/// <returns>
		///     The name of the application using the custom membership provider.
		/// </returns>
		public override string ApplicationName { get; set; }

		/// <summary>
		///     Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out.
		/// </summary>
		/// <returns>
		///     The number of invalid password or password-answer attempts allowed before the membership user is locked out.
		/// </returns>
		public override int MaxInvalidPasswordAttempts
		{
			get { return maxInvalidPasswordAttempts; }
		}

		/// <summary>
		///     Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
		/// </summary>
		/// <returns>
		///     The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
		/// </returns>
		public override int PasswordAttemptWindow
		{
			get { return passwordAttemptWindow; }
		}

		/// <summary>
		///     Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
		/// </summary>
		/// <returns>
		///     true if the membership provider requires a unique e-mail address; otherwise, false. The default is true.
		/// </returns>
		public override bool RequiresUniqueEmail
		{
			get { return requiresUniqueEmail; }
		}

		/// <summary>
		///     Gets a value indicating the format for storing passwords in the membership data store.
		/// </summary>
		/// <returns>
		///     One of the <see cref="T:System.Web.Security.MembershipPasswordFormat" /> values indicating the format for storing passwords in the data store.
		/// </returns>
		public override MembershipPasswordFormat PasswordFormat
		{
			get { return passwordFormat; }
		}

		/// <summary>
		///     Gets the minimum length required for a password.
		/// </summary>
		/// <returns>
		///     The minimum length required for a password.
		/// </returns>
		public override int MinRequiredPasswordLength
		{
			get { return minRequiredPasswordLength; }
		}

		/// <summary>
		///     Gets the minimum number of special characters that must be present in a valid password.
		/// </summary>
		/// <returns>
		///     The minimum number of special characters that must be present in a valid password.
		/// </returns>
		public override int MinRequiredNonAlphanumericCharacters
		{
			get { return minRequiredNonAlphaNumericCharacters; }
		}

		/// <summary>
		///     Gets the regular expression used to evaluate a password.
		/// </summary>
		/// <returns>
		///     A regular expression used to evaluate a password.
		/// </returns>
		public override string PasswordStrengthRegularExpression
		{
			get { return passwordStrengthRegularExpression; }
		}

		#endregion

		public override void Initialize(string name,
										NameValueCollection config)
		{
			try
			{
				#region assign values

				ProviderName = name;
				base.Initialize(name, config);

				if (!int.TryParse(config["loginHistoryRetentionInDays"], out loginHistoryRetentionInDays))
				{
					loginHistoryRetentionInDays = 180;
				}
				Logger.Trace(t => t("loginHistoryRetentionInDays: {0}", loginHistoryRetentionInDays));

				if (!int.TryParse(config["loginHistoryRetentionInRecords"], out loginHistoryRetentionInRecords))
				{
					loginHistoryRetentionInRecords = 0;
				}
				Logger.Trace(t => t("loginHistoryRetentionInRecords: {0}", loginHistoryRetentionInRecords));

				if (!int.TryParse(config["minSaltCharacters"], out minSaltCharacters))
				{
					minSaltCharacters = 30;
				}
				Logger.Trace(t => t("minSaltCharacters: {0}", minSaltCharacters));

				if (!int.TryParse(config["maxSaltCharacters"], out maxSaltCharacters))
				{
					maxSaltCharacters = 60;
				}
				Logger.Trace(t => t("maxSaltCharacters: {0}", maxSaltCharacters));

				if (!int.TryParse(config["sessionTime"], out sessionTime))
				{
					sessionTime = 20;
				}
				Logger.Trace(t => t("sessionTime: {0}", sessionTime));

				if (!int.TryParse(config["lockoutTime"], out lockoutTime))
				{
					lockoutTime = 15;
				}
				Logger.Trace(t => t("lockoutTime: {0}", lockoutTime));

				#region base member initialization

				ApplicationName = config["applicationName"] ?? string.Empty;
				Logger.Trace(t => t("applicationName: {0}", ApplicationName));
				bool.TryParse(config["enablePasswordReset"], out enablePasswordReset);
				Logger.Trace(t => t("enablePasswordReset: {0}", enablePasswordReset));
				bool.TryParse(config["enablePasswordRetrieval"], out enablePasswordRetrieval);
				Logger.Trace(t => t("enablePasswordRetrieval: {0}", enablePasswordRetrieval));
				bool.TryParse(config["requiresQuestionAndAnswer"], out requiresQuestionAndAnswer);
				Logger.Trace(t => t("requiresQuestionAndAnswer: {0}", requiresQuestionAndAnswer));
				bool.TryParse(config["requiresUniqueEmail"], out requiresUniqueEmail);
				Logger.Trace(t => t("requiresUniqueEmail: {0}", requiresUniqueEmail));

				if (!int.TryParse(config["maxInvalidPasswordAttempts"], out maxInvalidPasswordAttempts))
				{
					maxInvalidPasswordAttempts = 0;
				}
				Logger.Trace(t => t("maxInvalidPasswordAttempts: {0}", maxInvalidPasswordAttempts));
				if (!int.TryParse(config["minRequiredNonAlphaNumericCharacters"], out minRequiredNonAlphaNumericCharacters))
				{
					minRequiredNonAlphaNumericCharacters = 0;
				}
				Logger.Trace(t => t("minRequiredNonAlphaNumericCharacters: {0}", minRequiredNonAlphaNumericCharacters));
				if (!int.TryParse(config["minRequiredPasswordLength"], out minRequiredPasswordLength))
				{
					minRequiredPasswordLength = 0;
				}
				Logger.Trace(t => t("minRequiredPasswordLength: {0}", minRequiredPasswordLength));
				if (!int.TryParse(config["passwordAttemptWindow"], out passwordAttemptWindow))
				{
					passwordAttemptWindow = 0;
				}
				Logger.Trace(t => t("passwordAttemptWindow: {0}", passwordAttemptWindow));
				if (!Enum.TryParse(config["passwordFormat"], out passwordFormat))
				{
					passwordFormat = MembershipPasswordFormat.Hashed;
				}
				Logger.Trace(t => t("passwordFormat: {0}", passwordFormat));
				passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"] ?? string.Empty;
				Logger.Trace(t => t("passwordStrengthRegularExpression: {0}", passwordStrengthRegularExpression));

				#endregion

				encryptionKey = config["encryptionKey"] != null ? Convert.FromBase64String(config["encryptionKey"]) : null;
				Commander.ConnectionStringName = config["connectionStringName"];
				Logger.Trace(t => t("connectionStringName: {0}", Commander.ConnectionStringName));

				passwordFormat = enablePasswordRetrieval ? MembershipPasswordFormat.Encrypted : MembershipPasswordFormat.Hashed;

				#endregion

				Commander.ApplicationName = ApplicationName;
				Commander.Initialize(ApplicationName, ProviderName, LockoutTime);

				#region validate settings

				if (!Commander.ConnectionIsGood())
				{
					throw new DataException(
						string.Format(
							"Unable to use the specified connection string \"{0}\".  Please verify your connection configuration.",
							Commander.ConnectionStringName));
				}

				if (enablePasswordRetrieval)
				{
					if (!Encryptor.ValidateKey(encryptionKey))
						throw new ArgumentException(
							"The provider has been configured to support reversible encryption, but the encryption key has not been supplied (or is not the proper size).");
				}

				if (minSaltCharacters > maxSaltCharacters)
				{
					throw new ArgumentException(
						string.Format(
							"The provider has been configured to generate salt characters where the minimum number of characters ({0}) is greater than the maximumn number of characters ({1}).",
							minSaltCharacters, maxSaltCharacters));
				}

				if (enablePasswordReset && !requiresQuestionAndAnswer)
				{
					Logger.Warn(
						w =>
						w(
							"The provider has been configured to allow password resets but the Questions and Anwsers are not marked as required.  This can lead to unresettable passwords."));
					;
				}

				if (minRequiredNonAlphaNumericCharacters < 0)
				{
					throw new ArgumentOutOfRangeException("minRequiredNonAlphaNumericCharacters");
				}

				if (minRequiredPasswordLength < 0)
				{
					throw new ArgumentOutOfRangeException("minRequiredPasswordLength");
				}

				if (minSaltCharacters < 0)
				{
					throw new ArgumentOutOfRangeException("minSaltCharacters");
				}

				if (maxInvalidPasswordAttempts < 0)
				{
					throw new ArgumentOutOfRangeException("minSaltCharacters");
				}

				if (maxSaltCharacters < 0)
				{
					throw new ArgumentOutOfRangeException("maxSaltCharacters");
				}

				if (sessionTime <= 0)
				{
					throw new ArgumentOutOfRangeException("sessionTime");
				}

				if (lockoutTime < 0)
				{
					throw new ArgumentOutOfRangeException("lockoutTime");
				}

				if (passwordAttemptWindow < 0)
				{
					throw new ArgumentOutOfRangeException("passwordAttemptWindow");
				}

				if (loginHistoryRetentionInDays < 0)
				{
					throw new ArgumentOutOfRangeException("loginHistoryRetentionInDays");
				}

				if (loginHistoryRetentionInRecords < 0)
				{
					throw new ArgumentOutOfRangeException("loginHistoryRetentionInRecords");
				}

				#endregion
			}
			catch (Exception ex)
			{
				Logger.Error(e => e("Initialize(string, NameValueCollection)", ex));
				throw new InitializationException(string.Format("Failed to initialize the membership provider: {0}", ex.Message), ex);
			}
		}

		#region Extended implementation


		/// <summary>
		///     Activates a pending membership account.
		/// </summary>
		/// <returns>
		///     true if the account is confirmed; otherwise, false.
		/// </returns>
		/// <param name="accountConfirmationToken">A confirmation token to pass to the authentication provider.</param>
		public override bool ConfirmAccount(string accountConfirmationToken)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Activates a pending membership account for the specified user.
		/// </summary>
		/// <returns>
		///     true if the account is confirmed; otherwise, false.
		/// </returns>
		/// <param name="userName">The user name.</param>
		/// <param name="accountConfirmationToken">A confirmation token to pass to the authentication provider.</param>
		public override bool ConfirmAccount(string userName,
											string accountConfirmationToken)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, creates a new user account using the specified user name and password, optionally requiring that the new account must be confirmed before the account is available for use.
		/// </summary>
		/// <returns>
		///     A token that can be sent to the user to confirm the account.
		/// </returns>
		/// <param name="userName">The user name.</param>
		/// <param name="password">The password.</param>
		/// <param name="requireConfirmationToken">(Optional) true to specify that the account must be confirmed; otherwise, false. The default is false.</param>
		public override string CreateAccount(string userName,
											 string password,
											 bool requireConfirmationToken)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, creates a new user profile and a new membership account.
		/// </summary>
		/// <returns>
		///     A token that can be sent to the user to confirm the user account.
		/// </returns>
		/// <param name="userName">The user name.</param>
		/// <param name="password">The password.</param>
		/// <param name="requireConfirmation">(Optional) true to specify that the user account must be confirmed; otherwise, false. The default is false.</param>
		/// <param name="values">(Optional) A dictionary that contains additional user attributes to store in the user profile. The default is null.</param>
		public override string CreateUserAndAccount(string userName,
													string password,
													bool requireConfirmation,
													IDictionary<string, object> values)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, deletes the specified membership account.
		/// </summary>
		/// <returns>
		///     true if the user account was deleted; otherwise, false.
		/// </returns>
		/// <param name="userName">The user name.</param>
		public override bool DeleteAccount(string userName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, generates a password reset token that can be sent to a user in email.
		/// </summary>
		/// <returns>
		///     A token to send to the user.
		/// </returns>
		/// <param name="userName">The user name.</param>
		/// <param name="tokenExpirationInMinutesFromNow">(Optional) The time, in minutes, until the password reset token expires. The default is 1440 (24 hours).</param>
		public override string GeneratePasswordResetToken(string userName,
														  int tokenExpirationInMinutesFromNow)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, returns all OAuth membership accounts associated with the specified user name.
		/// </summary>
		/// <returns>
		///     A list of all OAuth membership accounts associated with the specified user name.
		/// </returns>
		/// <param name="userName">The user name.</param>
		public override ICollection<OAuthAccountData> GetAccountsForUser(string userName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, returns the date and time when the specified user account was created.
		/// </summary>
		/// <returns>
		///     The date and time the account was created, or <see cref="F:System.DateTime.MinValue" /> if the account creation date is not available.
		/// </returns>
		/// <param name="userName">The user name of the account.</param>
		public override DateTime GetCreateDate(string userName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, returns the date and time when an incorrect password was most recently entered for the specified user account.
		/// </summary>
		/// <returns>
		///     The date and time when an incorrect password was most recently entered for this user account, or
		///     <see
		///         cref="F:System.DateTime.MinValue" />
		///     if an incorrect password has not been entered for this user account.
		/// </returns>
		/// <param name="userName">The user name of the account.</param>
		public override DateTime GetLastPasswordFailureDate(string userName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, returns the date and time when the password was most recently changed for the specified membership account.
		/// </summary>
		/// <returns>
		///     The date and time when the password was more recently changed for membership account, or
		///     <see
		///         cref="F:System.DateTime.MinValue" />
		///     if the password has never been changed for this user account.
		/// </returns>
		/// <param name="userName">The user name of the account.</param>
		public override DateTime GetPasswordChangedDate(string userName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, returns the number of times that the password for the specified user account was incorrectly entered
		///     since the most recent successful login or since the user account was created.
		/// </summary>
		/// <returns>
		///     The count of failed password attempts for the specified user account.
		/// </returns>
		/// <param name="userName">The user name of the account.</param>
		public override int GetPasswordFailuresSinceLastSuccess(string userName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, returns an ID for a user based on a password reset token.
		/// </summary>
		/// <returns>
		///     The user ID.
		/// </returns>
		/// <param name="token">The password reset token.</param>
		public override int GetUserIdFromPasswordResetToken(string token)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, returns a value that indicates whether the user account has been confirmed by the provider.
		/// </summary>
		/// <returns>
		///     true if the user is confirmed; otherwise, false.
		/// </returns>
		/// <param name="userName">The user name.</param>
		public override bool IsConfirmed(string userName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     When overridden in a derived class, resets a password after verifying that the specified password reset token is valid.
		/// </summary>
		/// <returns>
		///     true if the password was changed; otherwise, false.
		/// </returns>
		/// <param name="token">A password reset token.</param>
		/// <param name="newPassword">The new password.</param>
		public override bool ResetPasswordWithToken(string token,
													string newPassword)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region MembershipProvider implementation

		/// <summary>
		///     Adds a new membership user to the data source.
		/// </summary>
		/// <returns>
		///     A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the information for the newly created user.
		/// </returns>
		/// <param name="username">The user name for the new user. </param>
		/// <param name="password">The password for the new user. </param>
		/// <param name="email">The e-mail address for the new user.</param>
		/// <param name="passwordQuestion">The password question for the new user.</param>
		/// <param name="passwordAnswer">The password answer for the new user</param>
		/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
		/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
		/// <param name="status">
		///     A <see cref="T:System.Web.Security.MembershipCreateStatus" /> enumeration value indicating whether the user was created successfully.
		/// </param>
		public override MembershipUser CreateUser(string username,
												  string password,
												  string email,
												  string passwordQuestion,
												  string passwordAnswer,
												  bool isApproved,
												  object providerUserKey,
												  out MembershipCreateStatus status)
		{
			var userId = Guid.NewGuid();
			if (providerUserKey != null)
			{
				if (!(providerUserKey is Guid))
				{
					status = MembershipCreateStatus.InvalidProviderUserKey;
					Logger.Error(e => e(
						"This provider only supports user keys of type System.Guid.  If not specifying a user key, provide a null value for the providerUserKey argument."));
					return null;
				}
				userId = (Guid)providerUserKey;
			}

			if (Commander.UserExistsByUserId(userId))
			{
				status = MembershipCreateStatus.DuplicateProviderUserKey;
				Logger.Error(
					e => e("The user key {0} for this account already exists in the application.  It cannot be added again.", userId));
				return null;
			}

			if (string.IsNullOrWhiteSpace(username))
			{
				status = MembershipCreateStatus.InvalidUserName;
				Logger.Error(e => e("The provided user name is blank."));
				return null;
			}

			username = username.ToLowerInvariant().Trim();

			if (Commander.UserExistsByUserName(username))
			{
				status = MembershipCreateStatus.DuplicateUserName;
				Logger.Error(e => e("The specified username \"{0}\" already exists in the application.", username));
				return null;
			}

			email = (email ?? string.Empty).ToLowerInvariant().Trim();

			if (requiresUniqueEmail && Commander.UserExistsByEmail(email))
			{
				status = MembershipCreateStatus.DuplicateEmail;
				Logger.Error(
					e =>
					e(
						"The membership provider is configured to require unique email addresses, and \"{0}\" already exists in the application.",
						email));
				return null;
			}

			passwordQuestion = (passwordQuestion ?? string.Empty).Trim();
			passwordAnswer = NormalizeString((passwordAnswer ?? string.Empty));

			if (requiresQuestionAndAnswer &&
				(string.IsNullOrWhiteSpace(passwordQuestion) || string.IsNullOrWhiteSpace(passwordAnswer)))
			{
				status = MembershipCreateStatus.InvalidQuestion;
				Logger.Error(
					e =>
					e(
						"The membership provider is configured to require a valid Question and Answer, and one or both of these parameters are empty."));
				return null;
			}

			byte[] passwordAnswerHash = null;
			string passwordAnswerSalt = null;
			if (!string.IsNullOrWhiteSpace(passwordQuestion) && !string.IsNullOrWhiteSpace(passwordAnswer))
			{
				passwordAnswerSalt = Encryptor.GenerateSalt(minSaltCharacters, maxSaltCharacters);
				passwordAnswerHash = Encryptor.GenerateHash(passwordAnswer, passwordAnswerSalt);
			}

			var passwordLength = password.Length;
			if (passwordLength < minRequiredPasswordLength)
			{
				status = MembershipCreateStatus.InvalidPassword;
				Logger.Error(
					e =>
					e(
						"The membership provider is configured to require a minimum password length of {0} characters, and the provided password is {1} character(s) long.",
						minRequiredPasswordLength, passwordLength));
				return null;
			}

			if (!string.IsNullOrWhiteSpace(passwordStrengthRegularExpression))
			{
				var regex = new Regex(passwordStrengthRegularExpression);
				if (!regex.IsMatch(password))
				{
					status = MembershipCreateStatus.InvalidPassword;
					Logger.Error(
						e =>
						e(
							"The membership provider is configured to require the password to meet certain complexity requirements, and the provided password does not qualify."));
					return null;
				}
			}

			byte[] passwordBytes;
			var salt = string.Empty;
			if (enablePasswordRetrieval)
			{
				passwordBytes = Encryptor.EncryptString(password, encryptionKey);
			}
			else
			{
				salt = Encryptor.GenerateSalt(minSaltCharacters, maxSaltCharacters);
				passwordBytes = Encryptor.GenerateHash(password, salt);
			}

			return Commander.CreateStandardUser(
				username,
				salt,
				passwordBytes,
				string.Empty,
				email,
				out status,
				userId,
				passwordQuestion,
				passwordAnswerSalt,
				passwordAnswerHash,
				isApproved);
		}

		/// <summary>
		/// Reduces the points of disparity in an answer string for a greater possibility of a match.
		/// </summary>
		/// <param name="original"></param>
		/// <returns></returns>
		/// <remarks>
		/// The default implementation of this in the base class removes white space and punctuation,
		/// and renders everything to lowercase.
		/// </remarks>
		protected virtual string NormalizeString(string original)
		{
			if (string.IsNullOrEmpty(original)) return original;
			var cleanedUp = Regex.Replace(original, @"[^a-zA-Z0-9]", string.Empty);
			return cleanedUp.ToLower();
		}

		/// <summary>
		///     Processes a request to update the password question and answer for a membership user.
		/// </summary>
		/// <returns>
		///     true if the password question and answer are updated successfully; otherwise, false.
		/// </returns>
		/// <param name="username">The user to change the password question and answer for. </param>
		/// <param name="password">The password for the specified user. </param>
		/// <param name="newPasswordQuestion">The new password question for the specified user. </param>
		/// <param name="newPasswordAnswer">The new password answer for the specified user. </param>
		public override bool ChangePasswordQuestionAndAnswer(string username,
															 string password,
															 string newPasswordQuestion,
															 string newPasswordAnswer)
		{
			if (!ValidateUser(username, password))
			{
				Logger.Warn(w => w("Security Question update for {0} failed: password did not validate.", username));
				return false;
			}

			var answerSalt = Encryptor.GenerateSalt(minSaltCharacters, maxSaltCharacters);
			var answerHash = Encryptor.GenerateHash(newPasswordAnswer, answerSalt);
			Commander.UpdateQuestionAndAnswer(username, newPasswordQuestion, answerSalt, answerHash);
			Logger.Info(i => i("Updated security question for user \"{0}\".", username));
			return true;
		}

		/// <summary>
		///     Gets the password for the specified user name from the data source.
		/// </summary>
		/// <returns>
		///     The password for the specified user name.
		/// </returns>
		/// <param name="username">The user to retrieve the password for. </param>
		/// <param name="answer">The password answer for the user. </param>
		public override string GetPassword(string username,
										   string answer)
		{
			if (!enablePasswordRetrieval)
			{
				throw new InvalidOperationException("The provider has not been configured to enable password retrieval.");
			}

			if (!ValidateAnswer(username, answer))
			{
				throw new BadAnswerException();
			}

			var encryptedPassword = Commander.GetEncryptedPassword(username);
			var decryptedPassword = Encryptor.DecryptString(encryptedPassword, encryptionKey);

			return Encryptor.Encoding.GetString(decryptedPassword);
		}

		/// <summary>
		///     Processes a request to update the password for a membership user.
		/// </summary>
		/// <returns>
		///     true if the password was updated successfully; otherwise, false.
		/// </returns>
		/// <param name="username">The user to update the password for. </param>
		/// <param name="oldPassword">The current password for the specified user. </param>
		/// <param name="newPassword">The new password for the specified user. </param>
		public override bool ChangePassword(string username,
											string oldPassword,
											string newPassword)
		{
			if (!ValidateUser(username, oldPassword))
			{
				Logger.Warn(w => w("Password update for {0} failed: password did not validate.", username));
				return false;
			}
			return ChangePassword(username, newPassword);
		}

		public bool ChangePassword(string username,
									string newPassword)
		{
			try
			{
				if (passwordFormat == MembershipPasswordFormat.Encrypted)
				{
					var encryptedPassword = Encryptor.EncryptString(newPassword, encryptionKey);
					Commander.ChangeEncryptedPassword(username, encryptedPassword);
				}
				else
				{
					var salt = Encryptor.GenerateSalt(minSaltCharacters, maxSaltCharacters);
					var hashedPassword = Encryptor.GenerateHash(newPassword, salt);
					Commander.ChangeHashedPassword(username, salt, hashedPassword);
				}

				return true;
			}
			catch (Exception ex)
			{
				Logger.Error(e => e("ChangePassword(string, string): {0}", ex));
				throw;
			}
		}

		/// <summary>
		///     Resets a user's password to a new, automatically generated password.
		/// </summary>
		/// <returns>
		///     The new password for the specified user.
		/// </returns>
		/// <param name="username">The user to reset the password for. </param>
		/// <param name="answer">The password answer for the specified user. </param>
		public override string ResetPassword(string username,
											 string answer)
		{
			if (!ValidateAnswer(username, answer))
			{
				throw new BadAnswerException();
			}

			var newPassword = Convert.ToBase64String(Encryptor.GenerateKey());
			if (ChangePassword(username, newPassword))
			{
				return newPassword;
			}

			throw new PasswordChangeException("Unable to reset the password.  Please review the logs for more details.");
		}

		protected virtual bool ValidateAnswer(string username,
											string answer)
		{
			var storedAnswer = Commander.GetHashedAnswer(username);
			var hashedAnswer = Encryptor.GenerateHash(NormalizeString(answer), storedAnswer.Salt);
			return (Convert.ToBase64String(storedAnswer.Hash) == Convert.ToBase64String(hashedAnswer));
		}

		/// <summary>
		///     Updates information about a user in the data source.
		/// </summary>
		/// <param name="user">
		///     A <see cref="T:System.Web.Security.MembershipUser" /> object that represents the user to update and the updated information for the user.
		/// </param>
		public override void UpdateUser(MembershipUser user)
		{
			if (user == null) throw new ArgumentNullException("user");
			Commander.UpdateMembershipUser(user);
		}

		/// <summary>
		///     Verifies that the specified user name and password exist in the data source.
		/// </summary>
		/// <returns>
		///     true if the specified username and password are valid; otherwise, false.
		/// </returns>
		/// <param name="username">The name of the user to validate. </param>
		/// <param name="password">The password for the specified user. </param>
		public override bool ValidateUser(string username,
										  string password)
		{
			var validated = false;
			if (passwordFormat == MembershipPasswordFormat.Hashed)
			{
				var storedHash = Commander.GetHashedPassword(username);
				var hashed = Encryptor.GenerateHash(password, storedHash.Salt);
				validated = (Convert.ToBase64String(hashed) == Convert.ToBase64String(storedHash.Hash));
			}
			else
			{
				var storedPassword = Commander.GetEncryptedPassword(username);
				var decrypted = Encryptor.DecryptString(storedPassword, encryptionKey);
				var decryptedText = Encryptor.Encoding.GetString(decrypted);
				validated = (password == decryptedText);
			}

			if (!validated)
				Logger.Warn(w => w("User {0} failed to authenticate.", username));

			Commander.RecordValidationEventByUserName(username, validated);

			return validated;
		}

		/// <summary>
		///     Clears a lock so that the membership user can be validated.
		/// </summary>
		/// <returns>
		///     true if the membership user was successfully unlocked; otherwise, false.
		/// </returns>
		/// <param name="userName">The membership user whose lock status you want to clear.</param>
		public override bool UnlockUser(string userName)
		{
			try
			{
				Commander.UnlockUser(userName);
				return true;
			}
			catch (Exception ex)
			{
				Logger.Error(e => e("Failed to unlock user {0}:({1}) {2}", userName, ex.GetType(), ex.Message));
				return false;
			}
		}

		/// <summary>
		///     Gets user information from the data source based on the unique identifier for the membership user. Provides an option to update the
		///     last-activity date/time stamp for the user.
		/// </summary>
		/// <returns>
		///     A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the specified user's information from the data source.
		/// </returns>
		/// <param name="providerUserKey">The unique identifier for the membership user to get information for.</param>
		/// <param name="userIsOnline">
		///     true to update the last-activity date/time stamp for the user; false to return user information without
		///     updating the last-activity date/time stamp for the user.
		/// </param>
		public override MembershipUser GetUser(object providerUserKey,
											   bool userIsOnline)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
		/// </summary>
		/// <returns>
		///     A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the specified user's information from the data source.
		/// </returns>
		/// <param name="username">The name of the user to get information for. </param>
		/// <param name="userIsOnline">
		///     true to update the
		///     last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time
		///     stamp for the user.
		/// </param>
		public override MembershipUser GetUser(string username,
											   bool userIsOnline)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Gets the user name associated with the specified e-mail address.
		/// </summary>
		/// <returns>
		///     The user name associated with the specified e-mail address. If no match is found, return null.
		/// </returns>
		/// <param name="email">The e-mail address to search for. </param>
		public override string GetUserNameByEmail(string email)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Removes a user from the membership data source.
		/// </summary>
		/// <returns>
		///     true if the user was successfully deleted; otherwise, false.
		/// </returns>
		/// <param name="username">The name of the user to delete.</param>
		/// <param name="deleteAllRelatedData">
		///     true to delete data related
		///     to the user from the database; false to leave data related to the user in the database.
		/// </param>
		public override bool DeleteUser(string username,
										bool deleteAllRelatedData)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Gets a collection of all the users in the data source in pages of data.
		/// </summary>
		/// <returns>
		///     A <see cref="T:System.Web.Security.MembershipUserCollection" /> collection that contains a page of
		///     <paramref
		///         name="pageSize" />
		///     <see cref="T:System.Web.Security.MembershipUser" /> objects beginning at the page specified by
		///     <paramref
		///         name="pageIndex" />
		///     .
		/// </returns>
		/// <param name="pageIndex">
		///     The index of the page of results to return. <paramref name="pageIndex" /> is zero-based.
		/// </param>
		/// <param name="pageSize">The size of the page of results to return.</param>
		/// <param name="totalRecords">The total number of matched users.</param>
		public override MembershipUserCollection GetAllUsers(int pageIndex,
															 int pageSize,
															 out int totalRecords)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Gets the number of users currently accessing the application.
		/// </summary>
		/// <returns>
		///     The number of users currently accessing the application.
		/// </returns>
		public override int GetNumberOfUsersOnline()
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Gets a collection of membership users where the user name contains the specified user name to match.
		/// </summary>
		/// <returns>
		///     A <see cref="T:System.Web.Security.MembershipUserCollection" /> collection that contains a page of
		///     <paramref
		///         name="pageSize" />
		///     <see cref="T:System.Web.Security.MembershipUser" /> objects beginning at the page specified by
		///     <paramref
		///         name="pageIndex" />
		///     .
		/// </returns>
		/// <param name="usernameToMatch">The user name to search for.</param>
		/// <param name="pageIndex">
		///     The index of the page of results to return.
		///     <paramref name="pageIndex" /> is zero-based.
		/// </param>
		/// <param name="pageSize">The size of the page of results to return.</param>
		/// <param name="totalRecords">The total number of matched users.</param>
		public override MembershipUserCollection FindUsersByName(string usernameToMatch,
																 int pageIndex,
																 int pageSize,
																 out int totalRecords)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		///     Gets a collection of membership users where the e-mail address contains the specified e-mail address to match.
		/// </summary>
		/// <returns>
		///     A <see cref="T:System.Web.Security.MembershipUserCollection" /> collection that contains a page of
		///     <paramref
		///         name="pageSize" />
		///     <see cref="T:System.Web.Security.MembershipUser" /> objects beginning at the page specified by
		///     <paramref
		///         name="pageIndex" />
		///     .
		/// </returns>
		/// <param name="emailToMatch">The e-mail address to search for.</param>
		/// <param name="pageIndex">
		///     The index of the page of results to return.
		///     <paramref name="pageIndex" /> is zero-based.
		/// </param>
		/// <param name="pageSize">The size of the page of results to return.</param>
		/// <param name="totalRecords">The total number of matched users.</param>
		public override MembershipUserCollection FindUsersByEmail(string emailToMatch,
																  int pageIndex,
																  int pageSize,
																  out int totalRecords)
		{
			throw new NotImplementedException();
		}

		#endregion
	}
}